www.gusucode.com > Sphero Connectivity Package 程序工具箱matlab源码 > Sphero Connectivity Package/sphero.m

    classdef sphero<handle
%SPHERO Connect and communicate with Sphero
%   
%   OBJ = SPHERO() creates a connection to Sphero and assigns the 
%   connection to a handle. If you do not indicate the name of the device
%   to connect to, it searches for the paired Sphero devices and asks user
%   to select one of them to connect to
%   
%   obj = SPHERO(DEVICENAME) connects to the indicated device through
%   the default communication protocol
%   
%   sphero properties:
%       BackLEDBrightness       - Brightness of the LED which indicates the back of the Sphero
%       CollisionDetection      - Toggle Collision Detection 
%       Color                   - Color of LED on the Sphero 
%       DeviceName              - Bluetooth name of Sphero
%       Handshake               - Toggle handshaking between PC and Sphero 
%       InactivityTimeout       - Timeout for inactivity (in seconds) (default = 600) 
%       MotionTimeout           - Timeout for the last motion command (in seconds) (default = 2) 
%       ResponseTimeout         - Timeout for response to be received from Sphero (in seconds)  (default = 2)
%       Status                  - (Read-only) Status of connection with Sphero
%
%   sphero methods:
%       boost                   - Run Sphero in Boost mode, which causes it to movee with high speed in the current direction 
%       brake                   - Apply optimal braking to stop the Sphero 
%       calibrate               - Calibrate the original orientation of the Sphero 
%       connect                 - Connect to Sphero 
%       delete                  - Delete the object of the class
%       disconnect              - Disconnect from the Sphero 
%       hardwareinfo            - Create an object for hardware related information of Sphero 
%       interruptSensorPolling  - Interrupt the current sensor polling
%       logSensors              - Poll the Sphero for specific sensor data, and log the data to a file. 
%       ping                    - Ping the Sphero to check the connection and whether the Sphero is awake and active 
%       rawMotor                - Provide raw motor commands to control the motor speed 
%       readLocator             - Read current location of Sphero, component velocities, and speed over ground 
%       readSensor              - Read the current value of the indicated sensors 
%       roll                    - Move the Sphero along a specific direction 
%       sleep                   - Force Sphero to go to sleep 
%
%   Examples:
%       
%       %Create a Sphero object
%       sph = sphero('Sphero-GPG');
%
%       %Change color of the Sphero LED, and turn on the back LED
%       sph.Color = 'r';
%       sph.BackLEDBrightness = 255;
%       
%       %Calibrate the orientation of the sphero
%       calibrate(sph, 80);
%
%       %Command the Sphero to movewith a speed of 20 at 0 degrees,
%       %and then turn right after 1 second.
%       roll(sph, 70,0); %speed, angle
%       pause(1)
%       roll(sph, 70, 90);
%       pause(1);
%       brake(sph);
%       
%       %Query the Sphero for the distance travelled along X and Y
%       %axes since start, whish are measured based on encoder readings
%       [x, y] = readSensor(sph, {'distX', 'distY'});
% See Also:
%   HARDWAREINFO
%   <a href="matlab:showdemo('sphero_examples')">Sphero Connectivity Package Examples</a>

% Copyright 2015-2018, The MathWorks, Inc.
       
    properties
                
        %ResponseTimeout - Timeout for response to be received from Sphero (in seconds)(default = 2)
        ResponseTimeout = 2; 
        
        %BackLEDBrightness - Brightness of the LED which indicates the back of the Sphero
        % Value of 0 turns off the LED, and 255 sets it to its maximum
        % brightness
        BackLEDBrightness;
              
        %MotionTimeout - Timeout for the last motion command (in seconds) (default = 2)
        % Modify this value if you would like the 'roll' commands to keep
        % the sphero rolling, instead of stopping after some time.
        MotionTimeout;  
        
        %InactivityTimeout - Timeout for inactivity (in seconds) (default = 600)
        % Sphero would go to sleep after the specified period of
        % inactivity. The inactivity timer is reset every time an API
        % command is received ver Bluetooth
        InactivityTimeout;
        
        %CollisionDetection - Toggle Collision Detection
        CollisionDetection = 0; 
        
        %NOTE: To be considered in future
%         PermOptions %would have a set method
%         TempOptions %would have a set method
%        
    end
    
    properties(Hidden=true)
        Api % Object to the CommunicationApi that is used by Sphero
        Listeners % Listeners used for processing asynchronous responses
        SaveLedColor = 1; % Whether LED Color should persist across power cycles 
        CommunicationApi = 'BluetoothApi'; %Specify which child of CommunicationApi class is to be used for the current version       
    end
    
    properties(Access = private)
        PrevHeading = 0;
    end
    
    properties(Access = {?sphero, ?BluetoothApi})
       %SensorPolling - Temporarily save the data from the sensors being polled, before saving them to a file
       % It is a struct with the following fields:
       %    filename
       %    sensors
       %    freq
       %    samples
       %    samplesPerPacket
       %    packets
       %    prevValues - a struct with the name of sensors being polled as
       %                the field. Required as the previous values in file
       %                are overwritten when saving the data with same
       %                variable name. Otherwise the file would have to be
       %                read at every iteration, when a new packet has to
       %                be saved to file.
       SensorPolling 
    end
    
    properties (Dependent=true)
        Handshake; % Toggle handshaking between PC and Sphero
        Color; % Color of LED on the Sphero 

    end
    
     properties (Dependent=true, SetAccess = private)
       Status % Status of connection with Sphero
       DeviceName; % Bluetooth name of Sphero
    end
    
    properties(GetAccess = private, Constant, Hidden)
        RateResolution = 0.784; % Resolution of Rotation Rate of sphero
        RateMax = 400; % Maximum possible Rotation rate
    end
    
    methods(Static, Hidden)
        function varargout = simpleResponse(response)
        %SIMPLERESPONSE Checks if received response is valid or not
        %
        %   SIMPLERESPONSE(RESPONSE) would error out if the response is invalid
        %   
        %   valid = SIMPLERESPONSE(RESPONSE) returns 1 if reponse is
        %   valid, otherwise it will return 0
            if nargout
                if isempty(response)
                    varargout{1} = 1;
                else
                    varargout{1} = 0;
                end
            else
                if ~isempty(response)
                    err = MException('Sphero:simpleResponse:InvalidResponse', 'Invalid response received');
                    throwAsCaller(err);
                end
            end
        end  
    end
    methods(Static, Access = private)
        function [Mask, Mask2] = DataStreamingMask(sensors)
        %DATASTREAMINGMASK Create the Masks that are used to create the 
        % command to stream sensor data from Sphero
        
            Mask = uint32(0);
            Mask2 = uint32(0);
                       
            for i=1:length(sensors)
                switch sensors{i}
                    case'accelX'
                        Mask = bitset(Mask, 32);
                    case 'accelY'
                        Mask = bitset(Mask, 31);
                    case 'accelZ'
                        Mask = bitset(Mask, 30);
                    case 'gyroX'
                        Mask = bitset(Mask, 29);
                    case 'gyroY'
                        Mask = bitset(Mask, 28);
                    case 'gyroZ'
                        Mask = bitset(Mask, 27);
                    case 'rmotorEmfRaw'
                        Mask = bitset(Mask, 23);
                    case 'lmotorEmfRaw'
                        Mask = bitset(Mask, 22);
                    case 'lmotorPwmRaw'
                        Mask = bitset(Mask, 21);
                    case 'rmotorPwmRaw'
                        Mask = bitset(Mask, 20);
                    case 'imuPitch'
                        Mask = bitset(Mask, 19);
                    case 'imuRoll'
                        Mask = bitset(Mask, 18);
                    case 'imuYaw'
                        Mask = bitset(Mask, 17);
                    case 'accelXFilt'
                        Mask = bitset(Mask, 16);
                    case 'accelYFilt'
                        Mask = bitset(Mask, 15);
                    case 'accelZFilt'
                        Mask = bitset(Mask, 14);
                    case 'gyroXFilt'
                        Mask = bitset(Mask, 13);
                    case 'gyroYFilt'
                        Mask = bitset(Mask, 12);
                    case 'gyroZFilt'
                        Mask = bitset(Mask, 11);
                    case 'rmotorEmfFilt'
                        Mask = bitset(Mask, 7);
                    case 'lmotorEmfFilt'
                        Mask = bitset(Mask, 6);
                    case 'Q0'
                        Mask2 = bitset(Mask2, 32);
                    case 'Q1'
                        Mask2 = bitset(Mask2, 31);
                    case 'Q2'
                        Mask2 = bitset(Mask2, 30);
                    case 'Q3'
                        Mask2 = bitset(Mask2, 29);
                    case 'distX'
                        Mask2 = bitset(Mask2, 28);
                    case 'distY'
                        Mask2 = bitset(Mask2, 27);
                    case 'accelOne'
                        Mask2 = bitset(Mask2, 26);
                    case 'velX'
                        Mask2 = bitset(Mask2, 25);
                    case 'velY'
                        Mask2 = bitset(Mask2, 24);
                    otherwise
                        err = MException('Sphero:DataStreamingMask:IncorrectInput', ...
                            'Undefined sensor name');
                        throw(err);
                end
            end
        end
        
       
        function powerNotify(~, eventdata) 
        %POWERNOTIFY Callback for Listener for PowerNotification property of
        % CommunicationApi. By default it will just display a warning.
        
        % src.Name will be 'PowerNotification'
            warning(eventdata.AffectedObject.PowerNotification);
            lastwarn(''); %Added so that instrcb.m does not display the warning again
        end
        
        function preSleepWarning(~, eventdata)
        %PRESLEEPWARNING Callback for Listener for PreSleepWarning property of
        % CommunicationApi. By default it will just display a warning.
        
            if eventdata.AffectedObject.PreSleepWarning
                warning('Sphero is about to sleep due to inactivity. Disconnecting from the device');
                lastwarn(''); %Added so that instrcb.m does not display the warning again
                
                disconnect(eventdata.AffectedObject);
            end
               
        end
        
        function collisionDetect(~, eventdata)
        %COLLISIONDETECT Callback for Listener for CollisionDetection 
        % property of CommunicationApi. By default it will just display a warning
        
            warning('Collision occured along %c-direction', eventdata.AffectedObject.CollisionDetected);
            lastwarn(''); %Added so that instrcb.m does not display the warning again
        end
        
        function gyroAxisLimitExceed(~, ~)
        %GYROAXISLIMITEXCEEDED Callback for Listener for GyroAxisLimitExceed 
        % property of CommunicationApi. By default it will just display a warning
        
            warning('Gyro Axis Limit Exceeded');
            lastwarn(''); %Added so that instrcb.m does not display the warning again
        end
    end
    
    methods(Access = private)
                      
        function sensorDataStreaming(obj, ~, ~)
        %SENSORDATASTREAMING Callback for Listener for the SensorData 
        % property, for Logging the sensor data

            if isempty(obj.SensorPolling.filename)
                warning(['Sphero is sending Sensor Data that is to be polled'...
                    ', but settings for saving the sensor data have'...
                    ' not been set.']);
                return
            end
            
            if isempty(obj.Api.SensorData)
                return
            end
            
            %Loop through the list of sensors, add the current packet data
            %to obj.SensorPolling.prevValues and save the sensor data to file
            for i=1:length(obj.SensorPolling.sensors)
                if ~isfield(obj.Api.SensorData, obj.SensorPolling.sensors{i})
                    return
                end
                    
                currData = obj.Api.SensorData.(obj.SensorPolling.sensors{i});
                prevData = obj.SensorPolling.prevValues.(obj.SensorPolling.sensors{i});
                
                prevData(end+1:end+length(currData)) = currData;
                
                values.(obj.SensorPolling.sensors{i}) = prevData;
                obj.SensorPolling.prevValues.(obj.SensorPolling.sensors{i}) = prevData;
           end
            
            save(obj.SensorPolling.filename, 'values', '-append');

            samplesRemaining = obj.SensorPolling.samples-length(values.(obj.SensorPolling.sensors{1}));
             
             if samplesRemaining<=0
                 obj.SensorPolling.filename =[];
                 obj.SensorPolling.freq = [];
                 obj.SensorPolling.samples = [];
                 obj.SensorPolling.samplesPerPacket = [];
                 obj.SensorPolling.packets = [];
                 obj.SensorPolling.sensors = [];
                 obj.SensorPolling.prevValues = [];
                 
                 % Delete the listener for SensorData and set the property
                 % of Api class to reject further sensor data response
                 % packets
                 delete(obj.Listeners.SensorDataList);
                 obj.Api.RejectSensorDataResponsePacket   = 1;
             end
        end
        
         function varargout = heading(obj, angle)
         %HEADING Adjust the heading direction of the sphero
         % 
         %  HEADING(SPH, ANGLE) sets the new heading to ANGLE, where ANGLE
         %  is in degrees
         
            nargoutchk(0, 1)
             
            angle = mod(angle, 360);
            [responseexpected, seq] = sendCmd(obj.Api, 'setcal', [], [], [], angle);
            
            response = readResponse(obj.Api, responseexpected, seq, obj.ResponseTimeout);
              
            [varargout{1:nargout}] = sphero.simpleResponse(response);
              
        end
        
        function varargout = stabilization(obj, flag)
        %STABILIZATION Toggle the stabilization
        %   
        %   STABILIZATION(SPH, FLAG) toggles the stabilization property
        %   of the Sphero based on the FLAG value
        
            nargoutchk(0, 1)
            p = inputParser;
            addRequired(p, 'flag', @(x) isnumeric(x) && (x==0 || x==1));
            parse(p, flag);
            
            [responseexpected, seq] = sendCmd(obj.Api, 'setstabiliz', [], [], [], flag);
            
            response = readResponse(obj.Api, responseexpected, seq, obj.ResponseTimeout);
              
            [varargout{1:nargout}] = sphero.simpleResponse(response);
              
        end
    
        function varargout = configureCollisionDetection(obj, method, xt, xspd, yt, yspd, dead)
        %CONFIGURECOLLISIONDETECTION Configure the options for Collision Detection
        % 
        %   CONFIGURECOLLISIONDETECTION(SPH, METHOD, XT, XSPD, YT, YSPD, DEAD)
        %   sets the following options for the collision detection
        % 
        %   METHOD: 0 to disable, 1 to enable collision detection
        %   XT, YT: Threshold for X and Y axes of Sphero, at speed 0  
        %   XSPD, YSPD: Value added to XT and YT at maximum speed, for
        %   the threshold at maximum speed
        %   DEAD  : Dead time to prevent retrigerring. Specified in ms 
             p = inputParser;
            addRequired(p, 'objectname');
            addRequired(p, 'method', @(x) isnumeric(x) && (x==0 || x==1));
            addRequired(p, 'xt', @(x) isnumeric(x) && x>=intmin('uint8') && x<=intmax('uint8'));
            addRequired(p, 'xspd', @(x) isnumeric(x) && x>=intmin('uint8') && x<=intmax('uint8'));
            addRequired(p, 'yt', @(x) isnumeric(x) && x>=intmin('uint8') && x<=intmax('uint8'));
            addRequired(p, 'yspd', @(x) isnumeric(x) && x>=intmin('uint8') && x<=intmax('uint8'));
            addRequired(p, 'dead', @(x) isnumeric(x) && x>=intmin('uint8') && x<=intmax('uint8')); %Specified in ms. Sphero requires dead time in 10ms increments
            parse(p, obj, method, xt, xspd, yt, yspd, dead);
            
            nargoutchk(0, 1)
                        
            try
                [responseexpected, seq] = sendCmd(obj.Api, 'setcollisiondet', [], [], [],  p.Results.method, p.Results.xt, p.Results.xspd, p.Results.yt, p.Results.yspd, p.Results.dead/10);

                response = readResponse(obj.Api, responseexpected, seq, obj.ResponseTimeout);

                [varargout{1:nargout}] = sphero.simpleResponse(response);
            catch exception
                throwAsCaller(exception)
            end
            
        end
        
        function varargout = configureLocator(obj, x, y, flag, yawtare)
        %CONFIGURELOCATOR Reset the location of the robot and change the alignment of locator coordinates with respect to IMU heading
        %
        %   CONFIGURELOCATOR(SPH, X, Y) configures the current X, Y
        %   coordinates of the Sphero
        %
        %   CONFIGURELOCATOR(SPH, FLAG, X, Y, YAWTARE) configures the
        %   internal locator of the Sphero based on the input values.
        %   X, Y denote the current coordinates of the Sphero on the ground
        %   in cm.
        %   FLAG determines whether the yaw tare value is automatically
        %   corrected when calibrate command is used. (default = 1).
        %   YAWTARE controls how the X, Y-plane is aligned with the
        %   Sphero's internal heading coordinate system. When this is set
        %   to 0, it means that when rotation of Sphero is 0 (yaw=0), it
        %   corresponds to facing down the +Y-axis. (default = 0)
        %
        %   RESULT = CONFIGURELOCATOR(SPH, X, Y) returns 1 if the command 
        %   succeeds, otherwise it returns 0
        
            nargoutchk(0, 1);
            p = inputParser;
            addRequired(p, 'objectname');
            addRequired(p, 'x', @(x) isnumeric(x) && x>=intmin('int16') && x<=intmax('int16'));
            addRequired(p, 'y', @(x) isnumeric(x) && x>=intmin('int16') && x<=intmax('int16'));
            addOptional(p, 'flag', 1, @(x) isnumeric(x) && (x==0 || x==1));
            addOptional(p, 'yawtare', 0, @(x) isnumeric(x) && x>=intmin('int16') && x<=intmax('int16'));
            parse(p, obj, x, y, flag, yawtare);
                        
            [responseexpected, seq] = sendCmd(obj.Api, 'locator', [], [], [], p.Results.flag, p.Results.x, p.Results.y, p.Results.yawtare);
            
            response = readResponse(obj.Api, responseexpected, seq, obj.ResponseTimeout);
              
            [varargout{1:nargout}] = sphero.simpleResponse(response);
        end
        
        function setFlagsForMotionTimeout(obj)
        %SETFLAGSFORMOTIONTIMEOUT Set the permanent flags such that MotionTimeout property takes affect
        %
        %   RESULT = SETFLAGSFORMOTIONTIMEOUT(SPH) sets the permanent flags
        %   on the Sphero's config block, such that the MotionTimeout 
        %   command takes affect
        
           [responseexpected, seq] = sendCmd(obj.Api, 'getoptionsflag', [], 1, []);
           recoptionsflags = readResponse(obj.Api, responseexpected, seq, obj.ResponseTimeout);

           recoptionsflagsbits  = dec2bin(recoptionsflags(4), 8); % Just taking the last byte, as the response is received in Big Endian format
           sendoptionsflags     = char(recoptionsflagsbits);
           sendoptionsflags(4)  = '1'; % Set to enable motion timeouts;
           sendoptionsflags(7) = '1';
           
           [responseexpected, seq] = sendCmd(obj.Api, 'setoptionsflag', [], [], [], sendoptionsflags);
              
           result = readResponse(obj.Api, responseexpected, seq, obj.ResponseTimeout); 
           
            if responseexpected && ~isempty(result)
                 error('Sphero:FlagsForMotionTimeout:NotSet', 'Motion timeout could not be set. Please check the connection and retry'); 
            end
        end
    end
    
    methods
        function obj = sphero(varargin)
        %SPHERO    Create an object of class sphero 
        % 
        %   obj = SPHERO() searches for the paired Sphero devices and asks user
        %   to select one of them to connect to
        % 
        %   obj = SPHERO(DEVICENAME) connects to the indicated device through
        %   the communication protocol 
        % 
        %   obj = SPHERO(DEVICENAME, CONNECT, COMMUNICATIONAPI) connects 
        %   to the device using the indicated COMMUNICATIONAPI if 
        %   CONNECT is logically true. COMMUNICATIONAPI is a string,
        %   which corresponds to the name of a class which inherits from
        %   'Communication' class under sphero.internal package and is
        %   present in the package
        %
        %   Examples:
        %       
        %       %Create a Sphero object
        %       sph = sphero('Sphero-GPG');
            
           obj.Api = sphero.(obj.CommunicationApi);
            
           % Add listeners to the events, with callbacks set to the 
           %default callback functions
           obj.Listeners.PowerNotificationList      = addlistener(obj.Api, 'PowerNotification',  'PostSet',  @sphero.powerNotify);
           obj.Listeners.PreSleepWarningList        = addlistener(obj.Api, 'PreSleepWarning', 'PostSet', @sphero.preSleepWarning);
           obj.Listeners.CollisionDetectionList     = addlistener(obj.Api, 'CollisionDetected', 'PostSet', @sphero.collisionDetect);
           obj.Listeners.GyroAxisLimitExceedList    = addlistener(obj.Api, 'GyroAxisLimitExceed', 'PostSet', @sphero.gyroAxisLimitExceed);
           obj.Listeners.SensorDataList             = [];
%            obj.Listeners.SensorDataList             = addlistener(obj.Api, 'SensorData',  'PostSet',  @obj.sensorDataStreaming);
%            obj.Listeners.SensorDataList.Enabled     = false;
                       
           if nargin>1
               if varargin{2}
                    connect(obj, varargin{1});
               else
                   return
               end   
           else
               connect(obj, varargin{:});
           end
           
           % Set the default values of the properties which have associated
           % set Methods
           obj.Handshake = 0;
           obj.BackLEDBrightness = 0;
                     
           setFlagsForMotionTimeout(obj); % Set the permanent flags such that MotionTimeout property takes affect
           obj.MotionTimeout = 2;
           obj.InactivityTimeout = 600;
                    
           obj.SensorPolling = struct('filename', [], 'freq', [], ...
                'samples', [], 'samplesPerPacket', [], 'packets', [], ...
                'sensors', [], 'prevValues', [], 'presentValues', []);
            
            interruptSensorPolling(obj); % Reset the previous sensor polling     
        end
        
        function result = ping(obj)
        %PING Ping the Sphero to check the connection and whether the Sphero is awake and active
        % 
        %   RESULT = PING(SPH) returns the result of the ping as 1 if the
        %   connection is active. Otherwise it returns 0.

            try
                [responseexpected, seq] = sendCmd(obj.Api, 'ping', [], 1);
           
                response = readResponse(obj.Api, responseexpected, seq, obj.ResponseTimeout);
                
            catch err
                warning(err.message);
                response = NaN;
            end
            
            if isempty(response)
                result = 1;
            else
                result = 0;
            end
            
        end
                   
        function connect(obj, varargin)
        %CONNECT Connect to Sphero
        % 
        %   CONNECT(SPH) would connect to the previous Sphero device if
        %   it is detected. Otherwise it will search for paired devices
        %   and prompt the user to select one
        % 
        %   CONNECT(SPH, DEVICENAME) would try to connect to the
        %   specified device
        
            % If currently connected to a sphero, ping it and check if a
            % response is received
            if ~isempty(obj.Api.Bt) && strcmp(obj.Api.Bt.Status, 'open')
                try
                    % Turn off warnings such that the 'ping' command does
                    % not display an unecessary warning for the user
                    origwarn = warning;
                    warning('off', 'all');
                    
                    pingresult = ping(obj);
                    
                    % Restore the warning state
                    warning(origwarn);
                                
                    %If connected to a Sphero currently, and the user wants to
                    %connect to a particular sphero
                    if pingresult
                        if nargin>1
                            if ~strcmp(obj.Api.DeviceName, varargin{1})
                            error('sphero:connect:AlreadyConnected', ...
                                ['Currently connected to a different sphero: %s.', ...
                                ' First disconnect with the current sphero,', ...
                                'in order to connect to %s.'],...
                                obj.Api.DeviceName, varargin{1});
                            else
                                %Already connected to the desired sphero
                                return
                            end
                        else
                            %Already connected to a Sphero
                            return
                        end
                    end
                    %If ping was unsuccessful, then connect to a Sphero
                catch e
                    if strcmp(e.identifier, 'sphero:connect:AlreadyConnected')
                        rethrow(e);
                    end
                    
                    %If some other error, then do nothing, and try to
                    %connect to desired device
                end
            end
            
            try
                 connect(obj.Api, varargin{:});
            catch exception
                throwAsCaller(exception);
            end
        end
        
        function varargout = roll(obj, speed, heading)
        %ROLL Move the Sphero along a specific direction
        %
        %   ROLL(SPH, SPEED, HEADING) would cause the Sphero to move
        %   at the angle specified by HEADING, with speed of SPEED.
        %   HEADING is a value from 0 to 360, specified with regards 
        %   to the original orientation that is set when the Sphero is 
        %   first connected, or calibrated to. 
        %   SPEED can be between -255 and 255
        %
        %   RESULT = ROLL(SPH, SPEED, HEADING) would return 1 if the
        %   command succeeds. Otherwise it returns 0.
            
             nargoutchk(0, 1)
             
             if speed>255||speed<-255
                error('Sphero:roll:InputOutOfBounds', 'Please enter a value for SPEED that is between -255 and 255');
             elseif speed<0
                 heading = heading+180;
                 speed = abs(speed);
            end

            heading = mod(heading, 360);
                          
            [responseexpected, seq] = sendCmd(obj.Api, 'roll', [], [], [], speed, heading, 1);
            response = readResponse(obj.Api, responseexpected, seq, obj.ResponseTimeout);
            
            [varargout{1:nargout}] = sphero.simpleResponse(response);
            
            obj.PrevHeading = heading;
        end
        
        function varargout = brake(obj)
        %BRAKE Apply optimal braking to stop the Sphero
        % 
        %   BRAKE(SPH) would apply optimal braking to zero speed
        %   
        %   RESULT = BRAKE(SPH) returns 1 if the command succeeds,
        %   otherwise it returns 0
               nargoutchk(0, 1)
             
              [responseexpected, seq]  = sendCmd(obj.Api, 'roll', [], [], [], 0, obj.PrevHeading, 0);
              response = readResponse(obj.Api, responseexpected, seq, obj.ResponseTimeout);
              
              [varargout{1:nargout}] = sphero.simpleResponse(response);
              
        end
       
        function varargout = logSensors(obj, rate, varargin)
        %LOGSENSORS Poll the Sphero for specific sensor data, and save the data to a file.
        % 
        %   LOGSENSORS(SPH, RATE) saves the data of the distance
        %   travelled along x and y direction of the robot (based on
        %   initial orientation) in a MAT file that is named log.mat by
        %   default. The sensors will be polled indefinitely at the rate 
        %   specified by RATE (0-400Hz), with 1 value per packet being 
        %   transmitted back to the machine
        %
        %   LOGSENSORS(SPH, RATE, FRAMESPERPACKET, PACKETS) can be used
        %   to configure the number of frames to be relayed back to the 
        %   Sphero per packet, and also specify the number of packets to
        %   be returned for the current sensors that are being polled
        %   (distX and distY by default)
        %
        %   LOGSENSORS(____, Name, Value) provides additional options 
        %   specified by one or more Name, Value pair arguments. Name must
        %   appear inside single quotes (''). You can specify several 
        %   name-value pair arguments in any order as Name1, Value1, ..., 
        %   NameN, ValueN: 
        %       'sensorname'  - Cell array of sensors to be polled
        %       'filename'    - Name of MAT file to which data should be saved
        %       'append'      - Specify 1 in order to append data to the file
        %
        %   RESULT = LOGSENSORS(SPH, RATE) returns 1 if the command succeeds,
        %   otherwise it returns 0
        % 
        %   Examples: 
        %       logSensors(sph, 1, 'sensorname', {'accelX', 'accelY'}, 'filename', 'myfile.mat', 'append', 1) 
            
            %Arguments
            % 1. Sphero object
            % 2. Rate at which sensors are to be polled 
            % 3. framesPerPacket:Number of frames to be transmitted per
            % packet that is sent (default = 1)
            % 4. packets: Number of packets to be transmitted (default = 0,
            % unlimited)
            % NVP: sensorname, file, format, append
            p = inputParser;
            
            defaultFramesPerPacket = 1;
            defaultPackets = 0;
            defaultSensors = {'distX', 'distY'};
            defaultFilename = 'log.mat';
            
            checkFramesPerPacket = @(x) isnumeric(x) && isscalar(x) && (x>0);
            
            checkPackets = @(x) isnumeric(x) && isscalar(x) && (x>=0) && ...
                (x<=intmax('uint8'));
            
            function valid = checkSensorName(x, validSensors)
                if iscell(x)
                    valid = all(cell2mat(cellfun(@(y) any(validatestring(y, validSensors)), x, 'UniformOutput', false)));
                else
                    valid = any(validatestring(x, validSensors));
                end
            end
            filename = '';
            function valid = checkFilename(x)
               [pathstr,name,ext] = fileparts(x);
               if isempty(ext)
                   ext = '.mat';
               elseif ~strcmp(ext, '.mat')
                   error('Sphero:logSensors:FileExt', 'File extension should be MAT');
               end
               
               filename = fullfile(pathstr, [name, ext]);
               fileId = fopen(filename, 'a');
               
               if fileId~=-1
                   valid=1;
                   fclose(fileId);
               else
                   error('Sphero:logSensors:UnableToOpenFile', 'Cannot open the file %s', filename)
               end
               
               
            end
            validSensors = {'accelX', 'accelY', 'accelZ', 'gyroX', 'gyroY', ...
                'gyroZ', 'rmotorEmfRaw', 'lmotorEmfRaw', 'lmotorPwmRaw', ...
                'rmotorPwmRaw', 'imuPitch', 'imuRoll', 'imuYaw', ...
                'accelXFilt', 'accelYFilt', 'accelZFilt', 'gyroXFilt', ...
                'gyroYFilt', 'gyroZFilt', 'rmotorEmfFilt', 'lmotorEmfFilt', ...
                'Q0', 'Q1', 'Q2', 'Q3', 'distX', 'distY', 'accelOne', ...
                'velX', 'velY'};
            
            addRequired(p, 'objectname')
            addRequired(p, 'rate', @(x) isnumeric(x) && x>=0 && x<=400);
            addOptional(p, 'framesPerPacket', defaultFramesPerPacket, checkFramesPerPacket);
            addOptional(p, 'packets', defaultPackets, checkPackets);
            addParamValue(p, 'sensorname', defaultSensors, @(x) checkSensorName(x, validSensors)); %#ok<NVREPL>
            addParamValue (p, 'filename', defaultFilename, @(x) checkFilename(x)); %#ok<NVREPL>
            addParamValue (p, 'append', 0, @(x) (x==0||x==1)); %#ok<NVREPL>
            
            parse(p, obj, rate, varargin{:});
            
            N = uint16(obj.Api.MaxSensorSampleRate/p.Results.rate);
            M = p.Results.framesPerPacket;
            Pcnt = p.Results.packets;
            
            if ~iscell(p.Results.sensorname) % Convert the sensorname to a cell array
                sensors = cellstr(p.Results.sensorname);
            else
                sensors = p.Results.sensorname;
            end
            
            sensors = cellfun(@(y) validatestring(y, validSensors), sensors, 'UniformOutput', false);
            
            [Mask, Mask2] = sphero.DataStreamingMask(sensors);
                        
            %Create a cell array of the sensor names sorted in the order
            %that response is expected.
            sortedSensors = {};
            for i=1:length(validSensors)
              if any(strcmp(sensors, validSensors{i}))
                    sortedSensors{end+1} = validSensors{i}; %#ok<AGROW>
              end
            end
                          
            % If previous polling is already in progress, then wait
            % till it completes
            warncount=0;
            while ~isempty(obj.SensorPolling.filename)
                if isinf(obj.SensorPolling.packets)
                    warning(['Interrupting previous sensor polling, '...
                        'which was set to unlimited streaming'])
                    break
                elseif warncount<1 % If the warning has not been displayed, then display it
                    warning(['Other sensors are being polled at present. '...
                    'Waiting till that completes. If you would like to ', ...
                    'interrupt the current sensor polling, press ctrl+c ', ...
                    'and use the interruptSensorPolling method']);
                    warncount=1;
                end
                
            end
            
            
            if ~isempty(filename) % If the user specified the filename,
                % and it was processed by the checkFilename function, then use the processed filename
                obj.SensorPolling.filename = filename;
            else
                obj.SensorPolling.filename  = p.Results.filename;
            end
            
            % Set the fields of 'SensorPolling' property so that it can be
            % used by the callback function
            obj.SensorPolling.freq = obj.Api.MaxSensorSampleRate/N;
            obj.SensorPolling.samples = M*Pcnt;
            obj.SensorPolling.samplesPerPacket = M;
            obj.SensorPolling.packets = Pcnt;
            obj.SensorPolling.sensors = sortedSensors;
            
            if Pcnt==0
                  obj.SensorPolling.samples = Inf;
                  obj.SensorPolling.packets = Inf;
            end
            
            for i=1:length(sortedSensors) % Initialize the SensorPolling.prevValue field for all the sensors that are to be polled
                obj.SensorPolling.prevValues.(sortedSensors{i}) = [];
                obj.SensorPolling.presentValues.(sortedSensors{i}) = [];
            end
            
            % If the file is to be appended to, check if the file contains
            % data from the same sensors that are to be polled. If they 
            % match, then append the data to the file. Otherwise overwrite 
            % the file
            if p.Results.append
                readData = load(obj.SensorPolling.filename);
                sensorsPolled = fieldnames(readData.values);
                
                if readData.freq==obj.SensorPolling.freq && ...
                    all(strcmp(sensorsPolled, obj.SensorPolling.sensors))
                
                     obj.SensorPolling.prevValues = readData.values;
                     samples = readData.samples+obj.SensorPolling.samples;
                     save(obj.SensorPolling.filename, samples, '-append')
                 
                else
                    %If the previous saved sensor data does not match the
                    %data that is to be polled, then overwrite the file
                    warning(['Cannot append to indicated file due to'...
                        'mismatch between saved data and data to be polled. '...
                        'Overwriting existing file']);
                    
                    freq = obj.SensorPolling.freq;
                    samples = obj.SensorPolling.samples;
                    save(obj.SensorPolling.filename, freq, samples)
                end
                
             else
                 freq = obj.SensorPolling.freq; %#ok<NASGU>
                 samples = obj.SensorPolling.samples; %#ok<NASGU>
                 save(obj.SensorPolling.filename, 'freq', 'samples')
            end
            
            %Set up listener to save the received Sensor Data, which is 
            % sent through an asynchronous response, to the file
            obj.Listeners.SensorDataList = addlistener(obj.Api, 'SensorData',  'PostSet',  @obj.sensorDataStreaming);
%             obj.Listeners.SensorDataList.Enabled     = true;
            
            % Set the property of Api class to accept sensor data response
            % packets
            obj.Api.RejectSensorDataResponsePacket = 0;
            
            [responseexpected, seq]  = sendCmd(obj.Api, 'setdatastreaming', [], [], [], N, M, Mask, Pcnt, Mask2);
            response = readResponse(obj.Api, responseexpected, seq, obj.ResponseTimeout);
              
            [varargout{1:nargout}] = sphero.simpleResponse(response);
        end
        
        function interruptSensorPolling(obj)
        %INTERRUPTSENSORPOLLING Interrupt the current sensor polling
        %
        %   INTERRUPTSENSORPOLLING(SPH) interrupts the current sensor
        %   polling such that the next command for reading the sensor
        %   values or logging them can be sent
        
            sensorDummy = {'accelX'}; 
            N = 1;
            M = 1;
            Pcnt = 1;
           
            [Mask, Mask2] = sphero.DataStreamingMask(sensorDummy);
            
            % Delete the listener for the SensorData property, as it 
            % cannot be used for reading and returning the sensor value
            delete(obj.Listeners.SensorDataList); 
            
            % Set the property of Api class to reject further sensor data 
            % response packets because we want to stop receiving any other
            % sensor polling
            obj.Api.RejectSensorDataResponsePacket   = 1;

            obj.SensorPolling.filename =[];
            obj.SensorPolling.prevValues = [];
            obj.SensorPolling.sensors = sensorDummy;
            obj.SensorPolling.freq = obj.Api.MaxSensorSampleRate/N;
            obj.SensorPolling.samples = M*Pcnt;
            obj.SensorPolling.samplesPerPacket = M;
            obj.SensorPolling.packets = Pcnt;   
            
            %Try to read a sensor value from the Sphero, which would
            %interrupt the current sensor streaming
            sendCmd(obj.Api, 'setdatastreaming', [], 0, [], N, M, Mask, Pcnt, Mask2);
        end 
        
        function [value, varargout] = readSensor(obj, sensors)
        %READSENSOR Read the current value of the indicated sensors
        % 
        %   VALUE = READSENSOR(SPH, SENSORNAME) returns the current value
        %   of the sensors as a structure with the field names being the
        %   same as the indicated sensor names indicated in SENSORNAME
        % 
        %   [VAL1, VAL2, ..., VALN] = READSENSOR(SPH, SENSORNAME) returns
        %   the current value read from the sensors into the individual
        %   outputs specified for the function. VAL1 would contain the
        %   value of the first sensor mentioned in SENSORNAME, and so
        %   forth.
        %   
        %   Refer to the file units.txt for the range and units of each
        %   sensor
        %   
        %   Examples:
        %
        %       [distx, velx] = readSensor(sph, {'distX', 'velX'});
            
            N = 1;
            M = 1;
            Pcnt = 1;
            
             validSensors = {'accelX', 'accelY', 'accelZ', 'gyroX', 'gyroY', ...
                'gyroZ', 'rmotorEmfRaw', 'lmotorEmfRaw', 'lmotorPwmRaw', ...
                'rmotorPwmRaw', 'imuPitch', 'imuRoll', 'imuYaw', ...
                'accelXFilt', 'accelYFilt', 'accelZFilt', 'gyroXFilt', ...
                'gyroYFilt', 'gyroZFilt', 'rmotorEmfFilt', 'lmotorEmfFilt', ...
                'Q0', 'Q1', 'Q2', 'Q3', 'distX', 'distY', 'accelOne', ...
                'velX', 'velY'};
            
            if ~iscell(sensors)
                sensors = cellstr(sensors);
            end

            p = inputParser;
            addRequired(p, 'sensors', @(x) all(cell2mat(cellfun(@(y) any(validatestring(y, validSensors)), sensors, 'UniformOutput', false))));

            parse(p, sensors);
            
            sensors = cellfun(@(y) validatestring(y, validSensors), p.Results.sensors, 'UniformOutput', false);
            
            sensornum = length(sensors);
            
            %Create a cell array of the sensor names sorted in the order
            %that response is expected.
            sortedSensors = {};
            for i=1:length(validSensors)
              if any(strcmp(sensors, validSensors{i}))
                    sortedSensors{end+1} = validSensors{i}; %#ok<AGROW>
              end
            end
            
            if nargout>1 && nargout~=sensornum
                error('Sphero:readSensor:NumOutputs', 'Expected number of outputs is either 1 or %i (number of sensors being read)', sensornum);
            end
            
            [Mask, Mask2] = sphero.DataStreamingMask(sortedSensors);
            
            % If previous polling is already in progress, then wait
            % till it completes            
            warncount=0;
            while ~isempty(obj.SensorPolling.filename)
                if isinf(obj.SensorPolling.packets)
                    warning(['Interrupting previous sensor polling, '...
                        'which was set to unlimited streaming'])
                    break
                elseif warncount<1
                    warning(['Other sensors are being polled at present. '...
                    'Waiting till that completes']);
                    warncount=1;
                end                
            end
            
            % Delete the listener for the SensorData property, as it 
            % cannot be used for reading and returning the sensor value
            delete(obj.Listeners.SensorDataList); 
            
            obj.SensorPolling.filename =[];
            obj.SensorPolling.prevValues = [];
            obj.SensorPolling.sensors = sortedSensors;
            obj.SensorPolling.freq = obj.Api.MaxSensorSampleRate/N;
            obj.SensorPolling.samples = M*Pcnt;
            obj.SensorPolling.samplesPerPacket = M;
            obj.SensorPolling.packets = Pcnt;   
            
            % Reset the SensorDataPropertySet property of 'Communication' 
            % class before we start polling it to check when the 
            % 'SensorData' property has been set due to data received from
            % the Sphero
            obj.Api.SensorDataPropertySet = 0;
            
            % Set the property of Api class to accept sensor data response
            % packets
            obj.Api.RejectSensorDataResponsePacket   = 0;
            
            sendCmd(obj.Api, 'setdatastreaming', [], 0, [], N, M, Mask, Pcnt, Mask2);
                                
            %Wait until SensorData property is set, due to data being 
            %received from sphero
            err = MException('Sphero:Api:ResponseTimeout', 'Response Timeout');
            t0 = tic;
            while ~obj.Api.SensorDataPropertySet

                pause(0.1) % Adding a pause. Otherwise the callback for 
                % asynchronous messages is not trigerred, as this loop 
                % keeps on executing and prevents the callback from being executed
                if toc(t0)>obj.ResponseTimeout
                    % Set the property of Api class to reject further sensor data 
                    % response packets
                    obj.Api.RejectSensorDataResponsePacket   = 1;
                    throw(err)
                end
            end
            
            if sensornum==1
                value = obj.Api.SensorData.(sensors{1});
            elseif nargout==1 || nargout==0
                value = orderfields(obj.Api.SensorData, sensors); %Retrieve sensor data when it is received
                % Reorder the data that is received in the 'sortedSensor'
                % fields, into the order that the user had input (i.e.
                % sensors), so that the returned values are in the same
                % order as the user requested
            else
                sensordata = struct2cell(orderfields(obj.Api.SensorData, sensors));
                
                value = sensordata{1};% sensordata.(sensors{1});
                
                varargout = sensordata(2:end);              
            end
            
            % Set the property of Api class to reject further sensor data 
            % response packets
            obj.Api.RejectSensorDataResponsePacket   = 1;
        end
        
        function [out1, varargout] = readLocator(obj)
        %READLOCATOR Read current location of Sphero, component velocities, and speed over ground
        %
        %   OUT = READLOCATOR(SPH) returns the x, y position, velocities
        %   along x and y direction, and the speed of the Sphero as a
        %   structure. The position is signed value in cm, velocities are
        %   signed in cm/sec, and the speed is unsigned in cm/sec.
        %   
        %   [XPOS, YPOS] = READLOCATOR(SPH) returns the x and y position of
        %   the Sphero with regards to the original position and
        %   orientation when it was calibrated.
        %
        %   [XPOS, YPOS, XVEL, YVEL, SPEED] = READLOCATOR(SPH) returns the 
        %   x, y position, velocities along x and y direction, and the 
        %   speed of the Sphero.
        
            nargoutchk(0, 5);
             
            [responseexpected, seq] = sendCmd(obj.Api, 'readlocator', [], 1, []);
            
            response = readResponse(obj.Api, responseexpected, seq, obj.ResponseTimeout);
              
            if nargout <2
                if ~iscell(response) && any(isnan(response))
                    out1 = NaN;
                else
                    result.xpos = response{1};
                    result.ypos = response{2};
                    result.xvel = response{3};
                    result.yvel = response{4};
                    result.speed = response{5};
                    out1 = result;
                end
            else
                if ~iscell(response) && any(isnan(response))
                    out1 = NaN;
                    varargout = num2cell(NaN(1, nargout-1));
                else
                    out1 = response{1};
                    varargout = response(2:nargout);
                end
            end
        end
        
        function varargout = calibrate(obj, angle)
        %CALIBRATE Calibrate the original orientation of the Sphero
        %   CALIBRATE(SPH, ANGLE) rotates the Sphero by angle specified by
        %   ANGLE, and sets that as the 0 heading angle for subsequent
        %   motions. The orientation of the Sphero can be observed by
        %   setting the Brightness of its back LED by using the
        %   BackLEDBrightness property
        %
        %   RESULT = CALIBRATE(OBJ, ANGLE) returns 1 if the command 
        %   succeeds, otherwise it returns 0
        %
        %   See also SPHERO.BACKLEDBRIGHTNESS
           if nargout
                roll(obj, 0, angle);
                pause(obj.ResponseTimeout); % pause for short while while the Sphero changes its orientation
                heading(obj, 0);
                configureLocator(obj, 0, 0, 0, 0);
           else
               result1 = roll(obj, 0, angle);
               pause(obj.ResponseTimeout);
               result2 = heading(obj, 0);
               result3 = configureLocator(obj, 0, 0, 0, 0);
               if result1 && result2 && result3
                   varargout{1} = 1;
               else
                   varargout{1} = 0;
               end
           end
           
           obj.PrevHeading = 0;
        end
        
        function varargout = boost(obj, varargin)
        %BOOST Run Sphero in Boost mode, which causes it to move with high speed in the specified direction
        %   BOOST(SPH) turns on the boost mode
        %
        %   BOOST(SPH, HEADING) causes the Sphero to move with high speed 
        %   in the indicated heading angle
        % 
        %   RESULT = BOOST(SPH) returns 1 if the command succeeds,
        %   otherwise it returns 0
        
            nargoutchk(0, 1);

            if nargin>1
                heading = varargin{1};
                heading = mod(heading, 360);
            else
                heading = 0;
            end
            
            if p.Results.flag
                speed = 255;
            else
                speed = 0;
            end
                          
            [responseexpected, seq] = sendCmd(obj.Api, 'roll', [], [], [], speed, heading, 1);
            
            response = readResponse(obj.Api, responseexpected, seq, obj.ResponseTimeout);
              
            [varargout{1:nargout}] = sphero.simpleResponse(response);
        end
        
        function varargout = rawMotor(obj, varargin)
        %RAWMOTOR Provide raw motor commands to control the motor speed
        %
        %   RAWMOTOR(SPH, Name, Value) takes in name-value pairs in order 
        %   to configure the motion of the motors. Name must appear inside 
        %   single quotes (''). You can specify several name-value pair 
        %   arguments in any order as Name1, Value1, ..., NameN, ValueN: 
        %       'left'      - Power value for left motor between 0 and 255 (default = 0)
        %       'right'     - Power value for right motor between 0 and 255 (default = 0)
        %       'leftmode'  - Mode for left motor (default = 'ignore')
        %       'rightmode' - Mode for right motor (default = 'ignore')
        %                     The mode can be one of the following:
        %                     'off','forward','reverse','brake','ignore'
        %
        %   Examples: 
        %       rawMotor(sph, 'left', 150, 'leftmode', 'forward') 
        %   
            nargoutchk(0, 1);
            narginchk(3,9);
            
            validmodes = {'off', 'forward', 'reverse', 'brake', 'ignore'};
            
            p = inputParser;
            addRequired(p, 'objectname');
            addParamValue (p, 'left', 0, @(x) isnumeric(x) && (x>=0 && x<=255)); %#ok<NVREPL>
            addParamValue (p, 'right', 0, @(x) isnumeric(x) && (x>=0 && x<=255)); %#ok<NVREPL>
            addParamValue (p, 'leftmode', 'ignore', @(x) any(validatestring(x, validmodes))); %#ok<NVREPL>
            addParamValue (p, 'rightmode', 'ignore', @(x) any(validatestring(x, validmodes))); %#ok<NVREPL>
            parse(p, obj, varargin{:}); 
            
            leftpower = p.Results.left;
            rightpower = p.Results.right;
            
            function mode = selectmode(modestring)
                switch modestring
                    case 'off'
                        mode = 0;
                    case 'forward'
                        mode = 1;
                    case 'reverse'
                        mode = 2;
                    case 'brake'
                        mode = 3;
                    case 'ignore'
                        mode = 4;
                end
            end
            
            leftmode = selectmode(p.Results.leftmode);
            rightmode = selectmode(p.Results.rightmode);
            
            [responseexpected, seq] = sendCmd(obj.Api, 'setrawmotors', [], [], [], leftmode, leftpower, rightmode, rightpower);
            
            response = readResponse(obj.Api, responseexpected, seq, obj.ResponseTimeout);
              
            [varargout{1:nargout}] = sphero.simpleResponse(response);
            
            pause(obj.ResponseTimeout);
            
            if obj.Handshake
                result = stabilization(obj, 1); % Turn on stabilization again after running this command
                
                if ~result
                    error('Sphero:rawMotor:Stabilization', 'Unable to set stabilization again, after running rawMotor command');
                end
            else
                stabilization(obj, 1);
            end  
        end
   
        function disconnect(obj)
        %DISCONNECT Disconnect from the Sphero
        %
        %   DISCONNECT(SPH) disconnects from the connected Sphero device
            try
                if ~isempty(obj.Api.Bt) && strcmp(obj.Status, 'open')
                    obj.BackLEDBrightness = 0;
                    disconnect(obj.Api);
                else
                     warning('Already disconnected from sphero')
                end
                
            catch e
                obj.Api.Bt = []; %Clear the Bluetooth object if an error occurs
                if (strcmp(e.identifier,'instrument:fwrite:opfailed'))
                    warning('Unable to write to device. The Sphero might already be disconnected')
                else
                    rethrow(e);
                end
            end
        end
        
        function delete(obj)
        %DELETE Delete the Sphero object and close the connection
            
            if ~isempty(obj.Listeners)
                delete(obj.Listeners.PowerNotificationList);
                delete(obj.Listeners.PreSleepWarningList);
                delete(obj.Listeners.CollisionDetectionList);
                delete(obj.Listeners.GyroAxisLimitExceedList);
%                 delete(obj.Listeners.SensorDataList); 
            end
            
            if ~isempty(obj.Api.Bt) && isvalid(obj.Api.Bt) && strcmp(obj.Api.Bt.Status, 'open')
                obj.BackLEDBrightness = 0;
            end
            
            delete(obj.Api);
        end
        
         function varargout = sleep(obj, varargin)
         %SLEEP Force Sphero to go to sleep
         %  
         %  SLEEP(SPH) makes the sphero go to sleep
         % 
         %  SLEEP(SPH, Name, Value) takes in name-value pairs in order 
         %  to configure what the Sphero should do on waking up. 
         %  Name must appear inside single quotes (''). You can specify 
         %  several name-value pair arguments in any order as Name1, 
         %  Value1, ..., NameN, ValueN: 
         %       'wakeup'    - The number of seconds Sphero should sleep
         %       for and then automatically reaqaken. 0 causes it to sleep
         %       forever (default = 0)
         %       'macro'     - If non-zero, Sphero will attempt to run this 
         %       macro ID upon wakeup(default = 0)
         %       'orbBasic'  - If non-zero, Sphero will attempt to run an
         %       orbBasic program in Flash from this line number (default =
         %       0)
    
            p = inputParser;
            addRequired(p, 'objectname');
            addParamValue (p, 'wakeup', 0, @(x) isnumeric(x) && (x>=0 && x<=intmax('uint16'))); %#ok<NVREPL>
            addParamValue (p, 'macro', 0, @(x) isnumeric(x) && (x>=0 && x<=255)); %#ok<NVREPL>
            addParamValue (p, 'orbBasic',  0, @(x) isnumeric(x) && (x>=0 && x<=intmax('uint16'))); %#ok<NVREPL>
            parse(p, obj, varargin{:}); 

            [responseexpected, seq] = sendCmd(obj.Api, 'sleep', [], [], [], p.Results.wakeup, p.Results.macro, p.Results.orbBasic);
            
            response = readResponse(obj.Api, responseexpected, seq, obj.ResponseTimeout);
              
            [varargout{1:nargout}] = sphero.simpleResponse(response);
              
            disconnect(obj.Api) 
        end
              
        function hwinfoObj = hardwareinfo(obj, varargin)
        %HARDWAREINFO Create an object for hardware related information of Sphero
        %   HWINFOOBJ = hardwareinfo(SPH) creates an object for hardware related
        %   information of Sphero
        %
        %   See also: 
        %       SPHERO.HWINFO 
        %       <a href="matlab:showdemo('sphero_examples')">Sphero Connectivity Package Examples</a>
            hwinfoObj = sphero.hwinfo(obj, varargin{:});
        end
    end
    
    methods
         % GET / SET methods
        function set.Color(obj, value)
        %set.Color Custom setter for Color property. Sends command to Sphero to change the color of LED
            message = sprintf(['The color should be specified as a RGB value',... 
                '(0 to 1, or 0 to %i) or one of the predefined `s'], ...
                obj.Api.Uint8Max);
            err = MException('Color:Invalild', message);
                    
             if ~isnumeric(value)
                switch value
                    case {'y', 'yellow'}
                        rgb = [1 1 0];
                    case {'m', 'magenta'}
                        rgb = [1 0 1];
                    case {'c', 'cyan'}
                        rgb = [0 1 1];
                    case {'r', 'red'}
                        rgb = [1 0 0];
                    case {'g', 'green'}
                        rgb = [0 1 0];
                    case {'b', 'blue'}
                        rgb = [0 0 1];
                    case {'w', 'white'}
                        rgb = [1 1 1];
                    case {'k', 'black'}
                        rgb = [0 0 0];
                    otherwise
                       throw(err);

                end
            elseif all(value<=(obj.Api.Uint8Max))
                rgb = value;
            else
                 throw(err);
            end
            
            if all(rgb<=1)
                rgb = uint8(rgb)*(obj.Api.Uint8Max);
            end
            
            [responseexpected, seq] = sendCmd(obj.Api, 'setrgbled', [], [], [], uint8(rgb), uint8(obj.SaveLedColor));
            response = readResponse(obj.Api, responseexpected, seq, obj.ResponseTimeout);
          
            if ~response
                error('Unable to set the desired Color of Sphero');
            end
        end
        
        function rgb = get.Color(obj)
        %get.Color Custom getter for Color property. Retrieves the Color from the Sphero
           [responseexpected, seq] = sendCmd(obj.Api, 'getrgbled', [], 1);
           
           rgb = readResponse(obj.Api, responseexpected, seq, obj.ResponseTimeout);
        end
        
        function set.Handshake(obj, value)
        %set.Handshake Custom setter for Color property
           obj.Api.Handshake = value;
        end
        
        function handshaking = get.Handshake(obj)
        %get.Handshake Custom getter for Color property
            handshaking = obj.Api.Handshake;
        end
          
        function status = get.Status(obj)
        %get.Status Custom getter for Status property
            status = obj.Api.Bt.status;
        end
        
        function devicename = get.DeviceName(obj)
        %get.DeviceName Custom getter for DeviceName property
            devicename = obj.Api.DeviceName;
        end
        
        function set.BackLEDBrightness(obj, brightness)
        %set.BackLEDBrightness Custom setter for BackLEDBrightness property    
            if brightness>0 && brightness<1
                brightness = uint8(brightness*(obj.Api.Uint8Max)); %#ok<MCSUP>
            end
            
             [responseexpected, seq] = sendCmd(obj.Api, 'setbackled', [], [],[], brightness); %#ok<MCSUP>
              
              result = readResponse(obj.Api, responseexpected, seq, obj.ResponseTimeout); %#ok<MCSUP>
              
              if responseexpected && ~isempty(result)
                 error('Sphero:BackLEDBrightness:NotSet', 'Brightness of Back LED could not be set. Please check the connection and retry') 
              end
              
              obj.BackLEDBrightness = brightness;
        end
                
        function set.SensorPolling(obj,val)
        %set.SensorPolling Custom setter for SensorPolling property    
           obj.SensorPolling = val;
           obj.Api.Sensors = obj.SensorPolling.sensors; %#ok<MCSUP>
           obj.Api.SamplesPerPacket = obj.SensorPolling.samplesPerPacket; %#ok<MCSUP>
           
        end
        
        function set.CollisionDetection(obj, flag)
        %set.CollisionDetection Custom setter for CollisionDetection property
            p = inputParser;
            addRequired(p, 'objectname');
            addRequired(p, 'flag', @(x) isnumeric(x) && (x==0 || x==1));
            parse(p, obj, flag);
            
            %Default values for collision detection parameters
            xt = 100;
            xspd = 100;
            yt = 100;
            yspd = 100;
            dead = 100; % 1 second
            
            if obj.Handshake %#ok<MCSUP>
                result = configureCollisionDetection(obj, p.Results.flag, xt, xspd, yt, yspd, dead);
                if ~result
                    error('Sphero:CollisionDetection:NoResponse', 'Collision Detection not configured. Please check connection and try again');
                end
            else
                configureCollisionDetection(obj, flag, xt, xspd, yt, yspd, dead);
            end
           
            obj.CollisionDetection = flag;
        end
        
        function set.MotionTimeout(obj, time)
        %set.MotionTimeout Custom setter for MotionTimeout property
            p = inputParser;
            addRequired(p, 'objectname');
            addRequired(p, 'time', @(x) isnumeric(x) && ((x>=0 && x<=intmax('uint16')/1000) || isinf(x)));
            parse(p, obj, time);
            
            if isinf(p.Results.time)
                time = intmax('uint16')/1000;
            end
            
            timeout = time*1000;
                        
            [responseexpected, seq] = sendCmd(obj.Api, 'setmotionto', [], [],[], timeout); %#ok<MCSUP>
              
            result = readResponse(obj.Api, responseexpected, seq, obj.ResponseTimeout); %#ok<MCSUP>
              
            if responseexpected && ~isempty(result)
                 error('Sphero:MotionTimeout:NotSet', 'Motion timeout could not be set. Please check the connection and retry'); 
            end
              
            obj.MotionTimeout = time;
        end
        
        function set.InactivityTimeout(obj, time)
        %set.InactivityTimeout Custom setter for InactivityTimeout property
            p = inputParser;
            addRequired(p, 'objectname');
            addRequired(p, 'time', @(x) isnumeric(x) && (x>=60 && x<=intmax('uint16')));
            parse(p, obj, time);
            
            [responseexpected, seq] = sendCmd(obj.Api, 'setinactivetimer', [], [],[], time); %#ok<MCSUP>
              
            result = readResponse(obj.Api, responseexpected, seq, obj.ResponseTimeout); %#ok<MCSUP>
              
            if responseexpected && ~isempty(result)
                 error('Sphero:InactivityTimeout:NotSet', 'Inactivity timeout could not be set. Please check the connection and retry'); 
            end
              
            obj.InactivityTimeout = time;
        end
    end
    
end